#include "game\MapGenerator.h"
#include "common\Debug.h"
#include <boost\range\algorithm\copy.hpp>

using namespace std;

void MapGenerator::generateCaveMap(Map* m)
{
	if(m == 0)
	{
		debug_printf("null pointer in generateCaveMap\n");
		return;
	}
  int* map = new int[m->getWidth() * m->getHeight()];
	int* swap = new int[m->getWidth() * m->getHeight()];
  
	for(int i=0;i<m->getWidth() * m->getHeight();i++) swap[i] = map[i] = 0;
  for(int x=2;x<m->getWidth()-2;x++) for(int y=2;y<m->getHeight()-2;y++) map[x + y * m->getWidth()] = (RNG.getInt(0,100) <= 40) ? 0 : 1; //0=solid, 1=open
	for(int i=0;i<4;i++)
	{
		for(int x=2;x<m->getWidth()-2;x++) for(int y=2;y<m->getHeight()-2;y++)
		{
			int R1 = 9;
			int R2 = 21;
			for(int xx=-1;xx<=1;xx++)for(int yy=-1;yy<=1;yy++) R1 -= map[x + xx + (y + yy) * m->getWidth()];
			for(int xx=-2;xx<=2;xx++)for(int yy=-2;yy<=2;yy++)
      {
        if(abs(xx) == 2 && abs(yy) == 2) continue;
        R2 -= map[x + xx + (y + yy) * m->getWidth()];
      }
			if(R1>=5||R2<=2) swap[x + y * m->getWidth()] = 0;
			else swap[x + y * m->getWidth()] = 1;
		}
    for(int i=0;i<m->getWidth() * m->getHeight();i++) map[i] = swap[i];
	}
	for(int i=0;i<3;i++)
	{
		for(int x=2;x<m->getWidth()-2;x++) for(int y=2;y<m->getHeight()-2;y++)
		{
			int R1 = 9;
      for(int xx=-1;xx<=1;xx++)for(int yy=-1;yy<=1;yy++) R1 -= map[x + xx + (y + yy) * m->getWidth()];
			if(R1>=5) swap[x + y * m->getWidth()] = 0;
			else swap[x + y * m->getWidth()] = 1;
		}
		for(int i=0;i<m->getWidth() * m->getHeight();i++) map[i] = swap[i];
	}
  for(int x=0;x<m->getWidth();x++) for(int y=0;y<m->getHeight();y++)
  {
    if(map[x + y * m->getWidth()] == 0) m->setTileType(Vec(x,y), SOLID);
    else m->setTileType(Vec(x,y), FLOOR);
  }
	  
  delete[] map;
  delete[] swap;

  if(addStairs(m) == false) debug_printf("addStairs failed\n");

	return;
}

void MapGenerator::generateDungeonMap(Map* m)
{
	if(m == 0)
	{
		debug_printf("null pointer in generateDungeonMap\n");
		return;
	}
	vector<node> corridorNodes;
	for(int i=0;i<50;i++)
	{
		Vec p(RNG.getInt(0, m->getWidth()), RNG.getInt(0, m->getHeight()));
		Vec size(RNG.getInt(5,15/* max(5, m->m_width - p.x)*/), RNG.getInt(5, 15/*max(5, m->m_height - p.y)*/));
		addRoom(m, corridorNodes, p, size);
	}
	makeCorridors(m, corridorNodes);
  if(addStairs(m) == false) debug_printf("addStairs failed\n");
	return;
}

bool MapGenerator::addRoom(Map* m, vector<node> &nodes, Vec p, Vec size)
{
  Vec ddir[] = {Vec(0,-1), Vec(1,0), Vec(0,1), Vec(-1,0)};
	if(m == 0) return false;
	if(!m->inBounds(p) || !m->inBounds(p + size)) return false;
	int w = m->getWidth();
	int h = m->getHeight();
	// check if there is something else in the way
	for(int y=0;y<size.y;y++) for(int x=0;x<size.x;x++)
	{
		if(m->getTileType(Vec(p.x + x, p.y + y)) != SOLID) return false;
	}
	for(int y=0;y<size.y;y++) for(int x=0;x<size.x;x++)
	{
		m->setTileType(Vec(p.x + x, p.y + y), FLOOR);
	}
	// add doors
	int door_count = 1 + RNG.getInt(0, 3) + RNG.getInt(0, (2 * size.x + 2 * size.y) / 10);
	for(int d = 0;d < door_count;d++)
	{
		// door position on the perimeter
		int pos = RNG.getInt(0, 2 * size.x + 2 * size.y - 1);
		Vec door_pos;
		Direction dir;
		if(pos < 2 * size.x)
		{
			door_pos.x = p.x + pos % size.x;
			door_pos.y = p.y - 1 + (pos / size.x) * (size.y + 1);
			if(pos < size.x) dir = NORTH;
			else dir = SOUTH;
		}
		else
		{
			pos -= 2 * size.x;
			door_pos.y = p.y + pos % size.y;
			door_pos.x = p.x - 1 + (pos / size.y) * (size.x + 1);
			if(pos < size.y) dir = WEST;
			else dir = EAST;
		}
		if(m->inBounds(door_pos) && m->getTileType(door_pos) == SOLID)
		{
			m->setTileType(door_pos, DOOR);
			node n;
			n.p = door_pos + ddir[dir];
			n.v = dir;
			nodes.push_back(n);
		}
	}
	return true;
}

void MapGenerator::makeCorridors(Map* m, vector<node> nodes)
{
  Vec ddir[] = {Vec(0,-1), Vec(1,0), Vec(0,1), Vec(-1,0)};
	if(m == 0) return;	
	for(int i=0;i<(int)nodes.size();i++)
	{
		Direction dir = (Direction)nodes[i].v;
		int size = 1 + RNG.getInt(0, 10);
		Vec p = nodes[i].p;
    while(size > 0 && m->inBounds(p) && m->getTileType(p) == SOLID)
		{			
			m->setTileType(p, FLOOR);
      p = p + ddir[dir];
			size--;      
		}
    if(m->inBounds(p) && m->getTileType(p) == SOLID)
    {
		  node n;
		  n.p = p;
		  n.v = RNG.getInt(0,3);
		  Direction idir = Direction((dir + 2) % 4);
		  if(n.v == idir)
		  {
			  n.v++;
			  nodes.push_back(n);
			  n.v-=2;
			  nodes.push_back(n);
		  }
		  else nodes.push_back(n);
    }
	}
}

bool MapGenerator::addStairs(Map* m)
{
  int success = 0;
  Vec up, down;
  for(int i=0;i<1000;i++)
  {
    up = Vec(RNG.getInt(0, m->getWidth()), RNG.getInt(0, m->getHeight()));    
    if(m->getTileType(up) == FLOOR) 
    {
      m->setTileType(up, STAIRS_UP);
      success++;
      break;
    }   
  }
  for(int i=0;i<1000;i++)
  {
    down = Vec(RNG.getInt(0, m->getWidth()), RNG.getInt(0, m->getHeight()));
    if(m->getTileType(down) == FLOOR) 
    {
      m->setTileType(down, STAIRS_DOWN);
      success++;
      break;
    }   
  }  
  if(success >=2 && m->path(up, down).size() > 0) return true;
  return false;
}